#include "U2GLESHardwareBufferManager.h"
#include "U2GLESHardwareVertexBuffer.h"
#include "U2Exception.h"
#include "U2LogManager.h"


U2EG_NAMESPACE_USING


GLESHardwareVertexBuffer::GLESHardwareVertexBuffer(U2HardwareBufferManagerBase* mgr, 
												   size_t vertexSize,
                                                   size_t numVertices,
                                                   U2HardwareBuffer::Usage usage,
                                                   bool useShadowBuffer)
    : U2HardwareVertexBuffer(mgr, vertexSize, numVertices, usage, false, useShadowBuffer)
{
    if (!useShadowBuffer)
    {
        U2_EXCEPT(U2Exception::ERR_INTERNAL_ERROR,
                    "Only supported with shadowBuffer",
                    "GLESHardwareVertexBuffer");
    }

    glGenBuffers(1, &mBufferId);
    GL_CHECK_ERROR;

    if (!mBufferId)
    {
        U2_EXCEPT(U2Exception::ERR_INTERNAL_ERROR,
                    "Cannot create GL ES vertex buffer",
                    "GLESHardwareVertexBuffer::GLESHardwareVertexBuffer");
    }

    glBindBuffer(GL_ARRAY_BUFFER, mBufferId);
    GL_CHECK_ERROR;
    glBufferData(GL_ARRAY_BUFFER, mSizeInBytes, NULL,
                 U2GLESHardwareBufferManager::getGLUsage(usage));
    GL_CHECK_ERROR;
}

GLESHardwareVertexBuffer::~GLESHardwareVertexBuffer()
{
    glDeleteBuffers(1, &mBufferId);
    GL_CHECK_ERROR;
}

void* GLESHardwareVertexBuffer::lockImpl(size_t offset,
                                       size_t length,
                                       LockOptions options)
{
    GLenum access = 0;

    if (mIsLocked)
    {
        U2_EXCEPT(U2Exception::ERR_INTERNAL_ERROR,
                    "Invalid attempt to lock an index buffer that has already been locked",
                    "GLESHardwareVertexBuffer::lock");
    }

    void* retPtr = 0;

	U2GLESHardwareBufferManager* glBufManager = static_cast<U2GLESHardwareBufferManager*>(U2HardwareBufferManager::getSingletonPtr());

	// Try to use scratch buffers for smaller buffers
    if (length < glBufManager->getGLMapBufferThreshold())
    {
		// if this fails, we fall back on mapping
        retPtr = glBufManager->allocateScratch((u2uint32)length);

        if (retPtr)
        {
            mLockedToScratch = true;
            mScratchOffset = offset;
            mScratchSize = length;
            mScratchPtr = retPtr;
            mScratchUploadOnUnlock = (options != HBL_READ_ONLY);

            if (options != HBL_DISCARD)
            {
				// have to read back the data before returning the pointer
                readData(offset, length, retPtr);
            }
        }
    }
    else
    {
        U2_EXCEPT(U2Exception::ERR_INTERNAL_ERROR,
                    "Invalid Buffer lockSize",
                    "GLESHardwareVertexBuffer::lock");
    }

#if defined(GL_GLEXT_PROTOTYPES)
    if (!retPtr)
	{
		// Use glMapBuffer
		glBindBuffer( GL_ARRAY_BUFFER, mBufferId );
		// Use glMapBuffer
		if(options == HBL_DISCARD)
		{
			// Discard the buffer
			glBufferData(GL_ARRAY_BUFFER, mSizeInBytes, NULL, 
                            U2GLESHardwareBufferManager::getGLUsage(mUsage));
            
		}
		if (mUsage & HBU_WRITE_ONLY)
			access = GL_WRITE_ONLY_OES;
        
		void* pBuffer = glMapBufferOES( GL_ARRAY_BUFFER, access);
        
		if(pBuffer == 0)
		{
			U2_EXCEPT(U2Exception::ERR_INTERNAL_ERROR, 
                        "Vertex Buffer: Out of memory", "GLESHardwareVertexBuffer::lock");
		}
        
		// return offsetted
		retPtr = static_cast<void*>(
			static_cast<unsigned char*>(pBuffer) + offset);
        
		mLockedToScratch = false;
	}
#endif
	mIsLocked = true;
    return retPtr;
}

void GLESHardwareVertexBuffer::unlockImpl(void)
{
    if (mLockedToScratch)
    {
        if (mScratchUploadOnUnlock)
        {
                // have to write the data back to vertex buffer
                writeData(mScratchOffset, mScratchSize, mScratchPtr,
                          mScratchOffset == 0 && mScratchSize == getSizeInBytes());
        }

        static_cast<U2GLESHardwareBufferManager*>(
            U2HardwareBufferManager::getSingletonPtr())->deallocateScratch(mScratchPtr);

        mLockedToScratch = false;
    }
    else
    {
#if defined(GL_GLEXT_PROTOTYPES)
		glBindBuffer(GL_ARRAY_BUFFER, mBufferId);
        
		if(!glUnmapBufferOES( GL_ARRAY_BUFFER ))
		{
			U2_EXCEPT(U2Exception::ERR_INTERNAL_ERROR, 
                        "Buffer data corrupted, please reload", 
                        "GLESHardwareVertexBuffer::unlock");
		}
#else
        U2_EXCEPT(U2Exception::ERR_INTERNAL_ERROR,
                    "Only locking to scratch is supported",
                    "GLESHardwareVertexBuffer::unlockImpl");
#endif
    }
    mIsLocked = false;
}

void GLESHardwareVertexBuffer::readData(size_t offset, size_t length,
    void* pDest)
{
    if (mUseShadowBuffer)
    {
        void* srcData = mpShadowBuffer->lock(offset, length, HBL_READ_ONLY);
        memcpy(pDest, srcData, length);
        mpShadowBuffer->unlock();
    }
    else
    {
        U2_EXCEPT(U2Exception::ERR_INTERNAL_ERROR,
                    "Read hardware buffer is not supported",
                    "GLESHardwareVertexBuffer::readData");
    }
}

void GLESHardwareVertexBuffer::writeData(size_t offset,
                                       size_t length,
                                       const void* pSource,
                                       bool discardWholeBuffer)
{
    glBindBuffer(GL_ARRAY_BUFFER, mBufferId);
    GL_CHECK_ERROR;

    // Update the shadow buffer
    if(mUseShadowBuffer)
    {
        void* destData = mpShadowBuffer->lock(offset, length,
                                              discardWholeBuffer ? HBL_DISCARD : HBL_NORMAL);
        memcpy(destData, pSource, length);
        mpShadowBuffer->unlock();
    }

    if (offset == 0 && length == mSizeInBytes)
    {
        glBufferData(GL_ARRAY_BUFFER, mSizeInBytes, pSource,
                     U2GLESHardwareBufferManager::getGLUsage(mUsage));
        GL_CHECK_ERROR;
    }
    else
    {
        if(discardWholeBuffer)
        {
            glBufferData(GL_ARRAY_BUFFER, mSizeInBytes, NULL, 
                            U2GLESHardwareBufferManager::getGLUsage(mUsage));
        }

        glBufferSubData(GL_ARRAY_BUFFER, offset, length, pSource);
        GL_CHECK_ERROR;
    }
}

void GLESHardwareVertexBuffer::_updateFromShadow(void)
{
    if (mUseShadowBuffer && mShadowUpdated && !mSuppressHardwareUpdate)
    {
        const void *srcData = mpShadowBuffer->lock(mLockStart,
                                                   mLockSize,
                                                   HBL_READ_ONLY);

        glBindBuffer(GL_ARRAY_BUFFER, mBufferId);
        GL_CHECK_ERROR;

        // Update whole buffer if possible, otherwise normal
        if (mLockStart == 0 && mLockSize == mSizeInBytes)
        {
            glBufferData(GL_ARRAY_BUFFER, mSizeInBytes, srcData,
                         U2GLESHardwareBufferManager::getGLUsage(mUsage));
            GL_CHECK_ERROR;
        }
        else
        {
            glBufferSubData(GL_ARRAY_BUFFER, mLockStart, mLockSize, srcData);
            GL_CHECK_ERROR;
        }

        mpShadowBuffer->unlock();
        mShadowUpdated = false;
    }
}
